package com.atguigu.senior.dao;

import com.atguigu.senior.util.JDBCUtilV2;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 将共性的数据库操作代码封装在BaseDAO类中
 */
public class BaseDAO {

    /**
     * 通用的增删改的方法
     *
     * @param sql    调用者要执行的sql语句
     * @param params SQL语句中占位符要赋值的参数
     * @return 受影响的行数
     */
    public int executeUpdate(String sql, Object... params) throws Exception {
        // 1. 通过JDBCUtilV2获取数据库的连接
        Connection connection = JDBCUtilV2.getConnection();
        // 2. 预编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 3. 为占位符赋值
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                // 占位符的索引从1开始, 而数组的索引从0开始
                preparedStatement.setObject(i + 1, params[i]);
            }
        }
        // 4. 执行SQL语句, 接收受影响的行数
        int row = preparedStatement.executeUpdate();
        // 5. 释放资源
        preparedStatement.close();
        // 只有事务为自动提交时, 才需要我们进行回收, 如果是手动的, 需要他们自己处理
        if (connection.getAutoCommit()) {
            JDBCUtilV2.release();
        }
        // 6. 返回结果
        return row;
    }

    /**
     * 通用查询 : 多行多列, 单行多列, 单行单列
     * 1. 多行多列 : List<Employee>
     * 2. 单行多列 : Employee
     * 3. 单行单列 : Object
     * <p>
     * 封装过程 :
     * 1. 返回的类型 : 泛型 : 类型不确定, 调用者知道, 调用时, 将此次查询结果的类型告知BaseDAO就可以了
     * 2. 返回的结果 : 通用 : List<T> 可以存储多个结果, 也可以存储一个结果 get(0)
     * 3. 结果的封装 : 反射, 要求调用者告知BaseDAO要封装的类对象, 通过反射获取属性并进行封装
     *
     * @param clazz  查询结果的类型
     * @param sql    要执行的SQL语句
     * @param params SQL语句中占位符要赋的值
     * @param <T>    泛型
     * @return List<T>
     * @throws Exception 抛出异常
     */
    public <T> List<T> selectAll(Class<T> clazz, String sql, Object... params) throws Exception {
        // 1. 通过JDBCUtilV2获取数据库的连接
        Connection connection = JDBCUtilV2.getConnection();

        // 2. 预编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3. 为占位符赋值
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                // 占位符的索引从1开始, 而数组的索引从0开始
                preparedStatement.setObject(i + 1, params[i]);
            }
        }

        // 4. 执行SQL语句, 接收返回的结果集
        ResultSet resultSet = preparedStatement.executeQuery();

        // 获取结果集中的元数据对象, 包含了列的数量和每个列的名称
        ResultSetMetaData metaData = resultSet.getMetaData();
        // 获取列的数量
        int columnCount = metaData.getColumnCount();
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            // 通过反射实例化对象
            T t = clazz.newInstance();

            // 根据列的数量进行循环
            for (int i = 1; i <= columnCount; i++) {
                // 获取列的名称
                String fieldName = metaData.getColumnLabel(i);
                // 获取当前行对应列的值
                Object object = resultSet.getObject(fieldName);
                // 通过反射获取对象的属性
                int fieldIndex = 0;
                while ((fieldIndex = fieldName.indexOf("_")) != -1) {
                    char c = fieldName.charAt(fieldIndex + 1);
                    fieldName = fieldName.replaceFirst("_" + c, String.valueOf(Character.toUpperCase(c)));
                }
                Field field = clazz.getDeclaredField(fieldName);
                // 突破封装
                field.setAccessible(true);
                // 为属性赋值
                field.set(t, object);
            }

            list.add(t);
        }

        // 5. 释放资源
        resultSet.close();
        preparedStatement.close();
        // 只有事务为自动提交时, 才需要我们进行回收, 如果是手动的, 需要他们自己处理
        if (connection.getAutoCommit()) {
            JDBCUtilV2.release();
        }

        // 6. 返回结果
        return list;
    }

    /**
     * 通用查询 : 在上面查询的集合结果中获取第一个结果, 简化了获取单行单列的获取, 单行多列的获取
     *
     * @param clazz  查询结果的类型
     * @param sql    要执行的SQL语句
     * @param params SQL语句中占位符要赋的值
     * @param <T>    泛型
     * @return List<T>
     * @throws Exception 抛出异常
     */
    public <T> T selectQueryBean(Class<T> clazz, String sql, Object... params) throws Exception {
        List<T> ts = selectAll(clazz, sql, params);
        if (ts != null && ts.size() > 0) {
            return ts.get(0);
        }
        return null;
    }

}
